相互 TLS 認証で API Gateway のバックエンドの S3 にアクセスできるかやってみた

相互 TLS 認証で API Gateway のバックエンドの S3 にアクセスできるかやってみた

Clock Icon2022.02.04

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、アノテーション テクニカルサポートチームの中野です。
API Gateway のバックエンドに S3 を設定するパターンと API Gateway の通信で相互 TLS 認証(mTLS)を行うパターンの組み合わせを試す機会があったので、以下に手順をまとめてみました。

構成

以下のような構成で構築してみます。

また、mTLS の詳しい仕組みについては、以下がわかりやすかったので参照ください。

やってみた

前提条件

構築するまえに、前提として API Gateway のカスタムドメインを作成するために、以下を準備しておきます。

  • Route53 へのドメインの登録
  • ACM で証明書発行

準備が完了したら、以下手順を進めてきます。

ステップ 1: S3 に静的ファイルをアップロード

まず、S3 を作成します。
何らかの適当な文字を描画する HTML ファイルをアップロードしておきます。

また、API Gateway から今回作った S3 のみにアクセスできるように、以下のような IAM ポリシーを作成しておきます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": [
                "arn:aws:s3:::<S3 バケット名>",
                "arn:aws:s3:::<S3 バケット名>/*"
            ],
            "Effect": "Allow"
        }
    ]
}

このポリシーで、API Gateway が信頼されたエンティティ(ロールをアタッチできる対象)となるように IAM ロールを作成します。
IAM ロールは、ステップ 2 の API Gateway を構築する際に利用します。

ステップ 2: API Gateway の構築

では、S3 へプロキシする API Gateway を作成します。
API のタイプは、REST API で作成していきます。

リソースを新たに作成します。 ここでは、/page というパスで作成しました。

リソースに対して GET メソッドを追加して、以下のような設定を行います。
注意点として、「パスの上書きの使用」の部分に {S3 バケット名}/{取得するファイル名} となるように記載します。
また、実行ロール部分は、ステップ 1 で作成した API Gateway から S3 へのアクセスの許可を行うための IAM ロールの ARN を記載します。

作成が完了したら、適切なステージ名でデプロイします。

なお、ここで API Gateway のデフォルトのエンドポイントを無効化しておきます。
デフォルトのエンドポイントを無効化していない状態で、相互 TLS 認証を API Gateway へ設定すると、クライアント証明書なしでのアクセスができてしまいますので、運用前に見落としがないように注意してください。

再度、設定を反映するために、再度ステージへデプロイしておきます。

ステップ 3: カスタムドメインの作成

カスタムドメインを作成します。

このとき、一旦相互 TLS 認証を無効にしたまま、ドメイン作成を実行します。

ステップ 4: クライアント証明書の発行

クライアント証明書を自分のローカルマシンで作成します。

以下のブログの手順で実行しました。

ルート CA の秘密鍵を作成します。

$ openssl genrsa -out RootCA.key 4096
Generating RSA private key, 4096 bit long modulus
......................................................................................................................................................++
..................................++
e is 65537 (0x10001)

続いて、自己署名 CA 証明書作成します。

$ openssl req -new -x509 -days 36500 -key RootCA.key -out RootCA.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:JP
State or Province Name (full name) []:FUKUOKA
Locality Name (eg, city) []:
Organization Name (eg, company) []:Annotation
Organizational Unit Name (eg, section) []:Technical Support
Common Name (eg, fully qualified host name) []:an-nakano ca
Email Address []:

クライアント側の秘密鍵を作成します。

$ openssl genrsa -out my_client.key 2048
Generating RSA private key, 2048 bit long modulus
.............................................................................................................................+++
.....................................+++
e is 65537 (0x10001)

秘密鍵をもとに CSR を作成します。

$ openssl req -new -key my_client.key -out my_client.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:JP
State or Province Name (full name) []:FUKUOKA
Locality Name (eg, city) []:
Organization Name (eg, company) []:Annotation
Organizational Unit Name (eg, section) []:Technical Support
Common Name (eg, fully qualified host name) []:an-nakano client
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:

最後にクライント証明書を作成します。

$ openssl x509 -req -in my_client.csr -CA RootCA.pem -CAkey RootCA.key -set_serial 01 -out my_client.pem -days 36500 -sha256
Signature ok
subject=/C=JP/ST=FUKUOKA/O=Annotation/OU=Technical Support/CN=an-nakano client
Getting CA Private Key

作成した CA 証明書を適当な S3 バケットに保存します。

$ aws s3 cp RootCA.pem s3://<CA証明書用S3バケット名>/
upload: ./RootCA.pem to s3://<CA証明書用S3バケット名>/RootCA.pem

ステップ 5: API Gateway で相互 TLS 認証を設定

では、ステップ 3 で作成したカスタムドメインに、ステップ 4 で作成した S3 にアップロード済みの CA 証明書を設定します。

カスタムドメインのステータスが「利用可能」になるまで待ちます。

最後に、カスタムドメインの API マッピング設定で、API Gateway のステージを関連付けます。

ここで、Route53 のエイリアスレコードで API Gateway へトラフィック転送するように設定して完了です。

ステップ 6: 検証

では、クライアント証明書ありで、curl でアクセスしてみます。

$ curl -v -i --key my_client.key --cert my_client.pem  https://api.example.com/page
*   Trying 54.249.165.209...
* TCP_NODELAY set
* Connected to api.example.com (54.249.165.209) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=example.com
*  start date: Nov 15 00:00:00 2021 GMT
*  expire date: Dec 14 23:59:59 2022 GMT
*  subjectAltName: host "api.example.com" matched cert's "*.example.com"
*  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7ffd2f008200)
> GET /page HTTP/2
> Host: api.example.com
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200
HTTP/2 200
< x-amzn-requestid: a0469407-1a6b-4472-xxxxxxxxxx
x-amzn-requestid: a0469407-1a6b-4472-xxxxxxxxxx
< x-amz-apigw-id: M_xxxxxxxxxx
x-amz-apigw-id: M_xxxxxxxxxx
< x-amzn-trace-id: Root=1-61fc83ab-xxxxxxxxxx
x-amzn-trace-id: Root=1-61fc83ab-xxxxxxxxxx
< content-type: application/json
content-type: application/json
< content-length: 38
content-length: 38
< date: Fri, 04 Feb 2022 01:38:51 GMT
date: Fri, 04 Feb 2022 01:38:51 GMT

<
<html>
  Hello World!! Yeah!!
</html>
* Connection #0 to host api.example.com left intact
* Closing connection 0

無事、アクセスできることを確認しました。

次に、クライアント証明書なしで、curl でアクセスしてみます。

$ curl -v -i https://api.example.com/page
*   Trying 18.180.7.23...
* TCP_NODELAY set
* Connected to api.example.com (18.180.7.23) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to api.example.com:443
* Closing connection 0
curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to api.example.com:443

アクセスされないことが確認できました。

最後に

API Gateway で S3 へ簡単にプロキシできる環境を構築できて、さらに API Gateway の認証として相互 TLS 認証を利用する例をまとめてみました。
クライアント証明書をもつユーザのみにサイトを公開するようなパターンに有用なのではないでしょうか。

アノテーション株式会社について

アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社 WEB サイトをご覧ください。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.